package galaxy

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"os"
	"path"
	"reflect"

	"gddgame.cc/galaxy/utils"
	"github.com/facebookgo/structtag"
	"gopkg.in/yaml.v2"
)

// ConfigFile config.json file info
type Config struct {
	data map[string]interface{}
}

func (config *Config) AttachConfig(subConfig interface{}, name string) {
	if config.data == nil {
		config.data = make(map[string]interface{})
	}
	config.data[name] = subConfig
}

func (config *Config) All() map[string]interface{} {
	return config.data
}

func (config *Config) Read(name string) interface{} {
	if config.data == nil {
		return nil
	}
	v, ok := config.data[name]
	if !ok {
		return nil
	}
	return v
}

func (config *Config) Exist(name string) bool {
	_, ok := config.data[name]
	return ok
}

func (config *Config) LoadFromReaderJson(configData io.Reader) error {
	if err := json.NewDecoder(configData).Decode(&config.data); err != nil {
		return err
	}
	return nil
}

func (config *Config) LoadFromReaderYaml(configData io.Reader) error {
	if err := yaml.NewDecoder(configData).Decode(&config.data); err != nil {
		return err
	}
	return nil
}

func (config *Config) LoadConfig(filename string) error {
	if _, err := os.Stat(filename); err != nil {
		return err
	}
	file, err := os.Open(filename)
	if err != nil {
		return err
	}
	defer file.Close()
	fileSuffix := path.Ext(filename)
	switch fileSuffix {
	case ".yaml":
		return config.LoadFromReaderYaml(file)
	case ".json":
		fallthrough
	default:
		return config.LoadFromReaderJson(file)
	}
}

func (config *Config) LoadName(name string) error {
	if _, err := os.Stat(name); err == nil {
		return config.LoadConfig(name)
	}
	var filename string
	var file *os.File
	var err error
	filename = name + ".yaml"
	if _, err = os.Stat(filename); err == nil {
		file, err = os.Open(filename)
		if err != nil {
			return err
		}
		defer file.Close()
		return config.LoadFromReaderYaml(file)
	}
	filename = name + ".json"
	if _, err = os.Stat(filename); err == nil {
		file, err = os.Open(filename)
		if err != nil {
			return err
		}
		defer file.Close()
		return config.LoadFromReaderYaml(file)
	}
	return errors.New("Config support yaml or json, not find match file")
}

func (config *Config) Bind(name string, conf interface{}, required bool) error {
	if inited {
		return fmt.Errorf("Server is running, do not register config")
	}
	c := config.Read(name)
	var r map[string]interface{}
	if c == nil {
		if required {
			return fmt.Errorf("Key: %s config is miss", name)
		} else {
			return nil
		}
	} else {
		switch t := c.(type) {
		case map[string]interface{}:
			r = t
		case map[interface{}]interface{}:
			r = make(map[string]interface{}, len(t))
			for k, v := range t {
				r[utils.ToString(k)] = v
			}
		default:
			return fmt.Errorf("Key: %s config struct not map", name)
		}
	}

	v := reflect.ValueOf(conf).Elem()
	switch conf.(type) {
	case *map[string]interface{}:
		v.Set(reflect.ValueOf(r))
		return nil
	}
	t := reflect.TypeOf(conf).Elem()
	for k := 0; k < t.NumField(); k++ {
		jf, jv, _ := structtag.Extract("json", string(t.Field(k).Tag))
		if !jf {
			jv = t.Name()
		}
		gf, gv, _ := structtag.Extract("galaxy", string(t.Field(k).Tag))
		required := true
		if gf {
			if gv == "option" {
				required = false
			} else if gv == "required" {
				required = true
			}
		}
		if r[jv] == nil {
			if required {
				return fmt.Errorf("Key: %s config need %s", name, jv)
			}
		} else {
			field := v.Field(k)
			fv := reflect.ValueOf(r[jv])
			switch fv.Kind() {
			case reflect.Struct:
				fallthrough
			case reflect.Slice:
				fallthrough
			case reflect.Map:
				vv := map[string]interface{}{}
				for key, value := range r[jv].(map[interface{}]interface{}) {
					vv[utils.ToString(key)] = value
				}
				b, err := json.Marshal(vv)
				if err != nil {
					return err
				}
				if err := json.Unmarshal(b, field.Addr().Interface()); err != nil {
					return err
				}
				//fmt.Println(i)
				//field.Set(reflect.ValueOf(i))
			default:
				if field.Type() != fv.Type() {
					fv = fv.Convert(field.Type())
				}
				field.Set(fv)
			}

		}
	}
	return nil
}
